=head1 NAME

  RTx::AssetTracker::Asset - an AssetTracker Asset object

=head1 SYNOPSIS

  use RTx::AssetTracker::Asset;

=head1 DESCRIPTION


=head1 METHODS

=begin testing 

use RTx::AssetTracker::Asset;

=end testing

=cut


package RTx::AssetTracker::Asset;

use strict;
no warnings qw(redefine);

use RTx::AssetTracker::Type;
use RTx::AssetTracker::Group;
use RT::URI::at;
use RTx::AssetTracker::IPs;
use RT::URI;

# {{{ LINKTYPEMAP
# A helper table for links mapping to make it easier
# to build and parse links between tickets

use vars '%LINKTYPEMAP';

%LINKTYPEMAP = (
    TicketOf => { Type => 'TicketOf',
                  Mode => 'Target', },
    Tickets => { Type => 'TicketOf',
                 Mode => 'Base', },
);

# }}}


# {{{ LINKDIRMAP
# A helper table for links mapping to make it easier
# to build and parse links between tickets

use vars '%LINKDIRMAP';

%LINKDIRMAP = (
    TicketOf => { Base => 'TicketOf',
                  Target => 'HasTicket', },
);

# }}}

sub LINKTYPEMAP   { return \%LINKTYPEMAP   }
sub LINKDIRMAP   { return \%LINKDIRMAP   }



# {{{ sub Load

=head2 Load

Takes a single argument. This can be a asset id or
asset name.  If the asset can't be loaded, returns undef.
Otherwise, returns the asset id.

=cut

sub Load {
    my $self = shift;
    my $id   = shift;

    #If we have an integer URI, load the asset
    if ( $id =~ /^\d+$/ ) {
        my ($assetid,$msg) = $self->LoadById($id);

        unless ($self->Id) {
            $RT::Logger->crit("$self tried to load a bogus asset: $id\n");
            return (undef);
        }
    }

    elsif ( $id ) {
        my ($assetid,$msg) = $self->LoadByCol('Name', $id);

        unless ($self->Id) {
            $RT::Logger->crit("$self tried to load a bogus asset named: $id\n");
            return (undef);
        }
    }

    else {
        $RT::Logger->warning("Tried to load a bogus asset id: '$id'");
        return (undef);
    }

    #Ok. we're loaded. lets get outa here.
    return ( $self->Id );

}

# }}}

# {{{ sub Create

=head2 Create (ARGS)

Arguments: ARGS is a hash of named parameters.  Valid parameters are:

  id
  Type  - Either a Type object or a Type Name
  Name -- The unique name of the asset
  Owner -- This asset's owner. either an RT::User object or this user\'s id
  Description -- A string describing the asset
  Status -- any valid status (Defined in RTx::AssetTracker::Type)
  Owner --
  Parent -- 
  Model --
  Location --
  CustomField-<n> -- a scalar or array of values for the customfield with the id <n>


Returns: ASSETID, Transaction Object, Error Message


=begin testing


=end testing

=cut

sub Create {
    my $self = shift;

    my %args = (
        id                 => undef,
        Type               => undef,
        Name               => undef,
        Description        => '',
        Status             => '',
        Owner              => undef,
        Admin              => undef,
        Parent             => undef,
        Model              => '',
        Location           => '',
        _RecordTransaction => 1,
        @_
    );

    my ( $ErrStr, $Owner, $resolved );
    my (@non_fatal_errors);

    my $TypeObj = RTx::AssetTracker::Type->new($RT::SystemUser);

    if ( ( defined( $args{'Type'} ) ) && ( !ref( $args{'Type'} ) ) ) {
        $TypeObj->Load( $args{'Type'} );
    }
    elsif ( ref( $args{'Type'} ) eq 'RTx::AssetTracker::Type' ) {
        $TypeObj->Load( $args{'Type'}->Id );
    }
    else {
        $RT::Logger->debug( $args{'Type'} . " not a recognized type object." );
    }

    #Can't create a asset without a type.
    unless ( defined($TypeObj) && $TypeObj->Id ) {
        $RT::Logger->debug("$self No type given for asset creation.");
        return ( 0, 0, $self->loc('Could not create asset. Type not set') );
    }

    #Now that we have a type, Check the ACLS
    unless (
        $self->CurrentUser->HasRight(
            Right  => 'CreateAsset',
            Object => $TypeObj
        )
      )
    {
        return (
            0, 0,
            $self->loc( "No permission to create assets of this type '[_1]'", $TypeObj->Name));
    }

    unless ( $TypeObj->IsValidStatus( $args{'Status'} ) ) {
        return ( 0, 0, $self->loc('Invalid value for status') );
    }

    #Since we have a type, we can set type defaults

    # {{{ Deal with setting the owner

    #if ( ref( $args{'Owner'} ) eq 'RT::User' ) {
        #$Owner = $args{'Owner'};
    #}

    #If we've been handed something else, try to load the current user.
    #elsif ( $args{'Owner'} ) {
        #$Owner = $self->CurrentUser->UserObj;

        #push( @non_fatal_errors,
                #$self->loc("Owner could not be set.") . " "
              #. $self->loc( "User '[_1]' could not be found.", $args{'Owner'} )
          #)
          #unless ( $Owner->Id );
    #}

    #If we have a proposed owner and they don't have the right
    #to own a asset, scream about it and make them not the owner
    #if (
            #( defined($Owner) )
        #and ( $Owner->Id )
        #and ( $Owner->Id != $RT::Nobody->Id )
        #and (
            #!$Owner->HasRight(
                #Object => $TypeObj,
                #Right  => 'OwnAsset'
            #)
        #)
      #)
    #{

        #$RT::Logger->warning( "User "
              #. $Owner->Name . "("
              #. $Owner->id
              #. ") was proposed "
              #. "as a asset owner but has no rights to own "
              #. "assets in "
              #. $TypeObj->Name );

        #push @non_fatal_errors,
          #$self->loc( "Owner '[_1]' does not have rights to own this asset.",
            #$Owner->Name
          #);

        #$Owner = undef;
    #}

    #If we haven't been handed a valid owner, make it nobody.
    #unless ( defined($Owner) && $Owner->Id ) {
        #$Owner = new RT::User( $self->CurrentUser );
        #$Owner->Load( $RT::Nobody->Id );
    #}

    # }}}

    $RT::Handle->BeginTransaction();

    my %params = (
        Type            => $TypeObj->Id,
        Name            => $args{'Name'},
        Description     => $args{'Description'},
        Status          => $args{'Status'},
        #Owner           => $Owner->Id,
        Parent          => $args{'Parent'},
        Model           => $args{'Model'},
        Location        => $args{'Location'},
    );

# Parameters passed in during an import that we probably don't want to touch, otherwise
    foreach my $attr qw(id Creator Created LastUpdated LastUpdatedBy) {
        $params{$attr} = $args{$attr} if ( $args{$attr} );
    }

    my ($id,$asset_message) = $self->SUPER::Create( %params);
    unless ($id) {
        $RT::Logger->crit( "Couldn't create a asset: " . $asset_message );
        $RT::Handle->Rollback();
        return ( 0, 0,
            $self->loc("Asset could not be created due to an internal error")
        );
    }

    my $create_groups_ret = $self->_CreateAssetGroups();
    unless ($create_groups_ret) {
        $RT::Logger->crit( "Couldn't create asset groups for asset "
              . $self->Id
              . ". aborting Asset creation." );
        $RT::Handle->Rollback();
        return ( 0, 0,
            $self->loc("Asset could not be created due to an internal error")
        );
    }

# Set the owner in the Groups table
# We denormalize it into the Asset table too because doing otherwise would
# kill performance, bigtime. It gets kept in lockstep thanks to the magic of transactionalization

    #$self->OwnerGroup->_AddMember(
        #PrincipalId       => $Owner->PrincipalId,
        #InsideTransaction => 1
    #);

    # {{{ Deal with setting up watchers

    foreach my $type ( "Admin", "Owner" ) {
        next unless ( defined $args{$type} );
        foreach my $watcher (
            ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) )
        {

            # If there is an empty entry in the list, let's get out of here.
            next unless $watcher;

            # we reason that all-digits number must be a principal id, not email
            # this is the only way to can add
            my $field = 'Email';
            $field = 'PrincipalId' if $watcher =~ /^\d+$/;

            my ( $wval, $wmsg );

            #if ( $type eq 'Admin' ) {

        # Note that we're using AddWatcher, rather than _AddWatcher, as we
        # actually _want_ that ACL check. Otherwise, random asset creators
        # could make themselves adminccs and maybe get asset rights. that would
        # be poor
                ( $wval, $wmsg ) = $self->AddWatcher(
                    Type   => $type,
                    $field => $watcher,
                    Silent => 1
                );
            #}
            #else {
                #( $wval, $wmsg ) = $self->_AddWatcher(
                    #Type   => $type,
                    #$field => $watcher,
                    #Silent => 1
                #);
            #}

            push @non_fatal_errors, $wmsg unless ($wval);
        }
    }

    # }}}

    # {{{ Add all the custom fields

    foreach my $arg ( keys %args ) {
        next unless ( $arg =~ /^CustomField-(\d+)$/i );
        my $cfid = $1;
        foreach
          my $value ( UNIVERSAL::isa( $args{$arg} => 'ARRAY' ) ? @{ $args{$arg} } : ( $args{$arg} ) )
        {
            next unless ( length($value) );

            # Allow passing in uploaded LargeContent etc by hash reference
            $self->_AddCustomFieldValue(
                (UNIVERSAL::isa( $value => 'HASH' )
                    ? %$value
                    : (Value => $value)
                ),
                Field             => $cfid,
                RecordTransaction => 0,
            );
        }
    }

    # }}}

    if ( $args{'_RecordTransaction'} ) {

        # {{{ Add a transaction for the create
        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
                                                     Type      => "Create",
        );

        if ( $self->Id && $Trans ) {

            $TransObj->UpdateCustomFields(ARGSRef => \%args);

            $RT::Logger->info( "Asset " . $self->Id . " created of type '" . $TypeObj->Name . "' by " . $self->CurrentUser->Name );
            $ErrStr = $self->loc( "Asset [_1] created of type '[_2]'", $self->Id, $TypeObj->Name );
            $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
        }
        else {
            $RT::Handle->Rollback();

            $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
            $RT::Logger->error("Asset couldn't be created: $ErrStr");
            return ( 0, 0, $self->loc( "Asset could not be created due to an internal error"));
        }

        $RT::Handle->Commit();
        return ( $self->Id, $TransObj->Id, $ErrStr );

        # }}}
    }
    else {

        # Not going to record a transaction
        $RT::Handle->Commit();
        $ErrStr = $self->loc( "Asset [_1] created of type '[_2]'", $self->Id, $TypeObj->Name );
        $ErrStr = join( "\n", $ErrStr, @non_fatal_errors );
        return ( $self->Id, $0, $ErrStr );

    }
}


# }}}

# {{{ Routines dealing with watchers.

# {{{ _CreateAssetGroups

=head2 _CreateAssetGroups

Create the asset groups and links for this asset.
This routine expects to be called from Asset->Create _inside of a transaction_

It will create two groups for this asset: Admin and Owner.

It will return true on success and undef on failure.

=begin testing

=end testing

=cut


sub _CreateAssetGroups {
    my $self = shift;

    my @types = qw(Owner Admin);

    foreach my $type (@types) {
        my $type_obj = RTx::AssetTracker::Group->new($self->CurrentUser);
        my ($id, $msg) = $type_obj->CreateRoleGroup(Domain => 'RTx::AssetTracker::Asset-Role',
                                                       Instance => $self->Id,
                                                       Type => $type);
        unless ($id) {
            $RT::Logger->error("Couldn't create a asset group of type '$type' for asset ".
                               $self->Id.": ".$msg);
            return(undef);
        }
     }
    return(1);

}

# }}}

# {{{ sub OwnerGroup

=head2 OwnerGroup

A constructor which returns an RT::Group object containing the owner of this asset.

=cut

sub OwnerGroup {
    my $self = shift;
    my $owner_obj = RTx::AssetTracker::Group->new($self->CurrentUser);
    $owner_obj->LoadAssetRoleGroup( Asset => $self->Id,  Type => 'Owner');
    return ($owner_obj);
}

# }}}

# {{{ sub AddWatcher

=head2 AddWatcher

AddWatcher takes a parameter hash. The keys are as follows:

Type        One of Admin

PrinicpalId The RT::Principal id of the user or group that's being added as a watcher

Email       The email address of the new watcher. If a user with this
            email address can't be found, a new nonprivileged user will be created.

If the watcher you\'re trying to set has an RT account, set the Owner paremeter to their User Id. Otherwise, set the Email parameter to their Email address.

=cut

sub AddWatcher {
    my $self = shift;
    my %args = (
        Type  => undef,
        PrincipalId => undef,
        Email => undef,
        @_
    );

    # {{{ Check ACLS
    #If the watcher we're trying to add is for the current user
    if ( $self->CurrentUser->PrincipalId  eq $args{'PrincipalId'}) {
        #  If it's an Admin and they don't have
        #   'WatchAsAdmin' or 'ModifyAsset', bail
        if ( $args{'Type'} eq 'Admin' ) {
            unless ( $self->CurrentUserHasRight('ModifyAsset')
                or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
                return ( 0, $self->loc('Permission Denied'))
            }
        }

        else {
            $RT::Logger->warning( "$self -> AddWatcher got passed a bogus type");
            return ( 0, $self->loc('Error in parameters to Asset->AddWatcher') );
        }
    }

    # If the watcher isn't the current user
    # and the current user  doesn't have 'ModifyAsset'
    # bail
    else {
        unless ( $self->CurrentUserHasRight('ModifyAsset') ) {
            return ( 0, $self->loc("Permission Denied") );
        }
    }

    # }}}

    return ( $self->_AddWatcher(%args) );
}

#This contains the meat of AddWatcher. but can be called from a routine like
# Create, which doesn't need the additional acl check
sub _AddWatcher {
    my $self = shift;
    my %args = (
        Type   => undef,
        Silent => undef,
        PrincipalId => undef,
        Email => undef,
        @_
    );


    my $principal = RT::Principal->new($self->CurrentUser);
    if ($args{'Email'}) {
        my $user = RT::User->new($RT::SystemUser);
        my ($pid, $msg) = $user->LoadByEmail($args{'Email'});
        if ($pid) {
            $args{'PrincipalId'} = $pid;
        }
    }
    if ($args{'PrincipalId'}) {
        $principal->Load($args{'PrincipalId'});
    }


    # If we can't find this watcher, we need to bail.
    unless ($principal->Id) {
            $RT::Logger->error("Could not load a user with the email address '".$args{'Email'}. "' to add as a watcher for asset ".$self->Id);
        return(0, $self->loc("Could not find that user"));
    }


    my $group = RTx::AssetTracker::Group->new($self->CurrentUser);
    $group->LoadAssetRoleGroup(Type => $args{'Type'}, Asset => $self->Id);
    unless ($group->id) {
        return(0,$self->loc("Group not found"));
    }

    if ( $group->HasMember( $principal)) {

        return ( 0, $self->loc('That principal is already a [_1] for this asset', $self->loc($args{'Type'})) );
    }


    my ( $m_id, $m_msg ) = $group->_AddMember( PrincipalId => $principal->Id,
                                               InsideTransaction => 1 );
    unless ($m_id) {
        $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id."\n".$m_msg);

        return ( 0, $self->loc('Could not make that principal a [_1] for this asset', $self->loc($args{'Type'})) );
    }

    unless ( $args{'Silent'} ) {
        $self->_NewTransaction(
            Type     => 'AddWatcher',
            NewValue => $principal->Id,
            Field    => $args{'Type'}
        );
    }

        return ( 1, $self->loc('Added principal as a [_1] for this asset', $self->loc($args{'Type'})) );
}

# }}}

# {{{ sub DeleteWatcher

=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }


Deletes a Asset watcher.  Takes two arguments:

Type  (one of Admin)

and one of

PrincipalId (an RT::Principal Id of the watcher you want to remove)
    OR
Email (the email address of an existing wathcer)


=cut


sub DeleteWatcher {
    my $self = shift;

    my %args = ( Type        => undef,
                 PrincipalId => undef,
                 Email       => undef,
                 @_ );

    unless ( $args{'PrincipalId'} || $args{'Email'} ) {
        return ( 0, $self->loc("No principal specified") );
    }
    my $principal = RT::Principal->new( $self->CurrentUser );
    if ( $args{'PrincipalId'} ) {

        $principal->Load( $args{'PrincipalId'} );
    }
    else {
        my $user = RT::User->new( $self->CurrentUser );
        $user->LoadByEmail( $args{'Email'} );
        $principal->Load( $user->Id );
    }

    # If we can't find this watcher, we need to bail.
    unless ( $principal->Id ) {
        return ( 0, $self->loc("Could not find that principal") );
    }

    my $group = RTx::AssetTracker::Group->new( $self->CurrentUser );
    $group->LoadAssetRoleGroup( Type => $args{'Type'}, Asset => $self->Id );
    unless ( $group->id ) {
        return ( 0, $self->loc("Group not found") );
    }

    # {{{ Check ACLS
    #If the watcher we're trying to add is for the current user
    if ( $self->CurrentUser->PrincipalId eq $args{'PrincipalId'} ) {

        #  If it's an AdminCc and they don't have
        #   'WatchAsAdminCc' or 'ModifyAsset', bail
        if ( $args{'Type'} eq 'Admin' or $args{'Type'} eq 'Owner' ) {
            unless (    $self->CurrentUserHasRight('ModifyAsset')
                     or $self->CurrentUserHasRight('WatchAsAdminCc') ) {
                return ( 0, $self->loc('Permission Denied') );
            }
        }

        else {
            $RT::Logger->warn("$self -> DeleteWatcher got passed a bogus type");
            return ( 0,
                     $self->loc('Error in parameters to Asset->DeleteWatcher') );
        }
    }

    # If the watcher isn't the current user
    # and the current user  doesn't have 'ModifyAsset' bail
    else {
        unless ( $self->CurrentUserHasRight('ModifyAsset') ) {
            return ( 0, $self->loc("Permission Denied") );
        }
    }

    # }}}

    # see if this user is already a watcher.

    unless ( $group->HasMember($principal) ) {
        return ( 0,
                 $self->loc( 'That principal is not a [_1] for this asset',
                             $args{'Type'} ) );
    }

    my ( $m_id, $m_msg ) = $group->_DeleteMember( $principal->Id );
    unless ($m_id) {
        $RT::Logger->error( "Failed to delete "
                            . $principal->Id
                            . " as a member of group "
                            . $group->Id . "\n"
                            . $m_msg );

        return (0,
                $self->loc(
                    'Could not remove that principal as a [_1] for this asset',
                    $args{'Type'} ) );
    }

    unless ( $args{'Silent'} ) {
        $self->_NewTransaction( Type     => 'DelWatcher',
                                OldValue => $principal->Id,
                                Field    => $args{'Type'} );
    }

    return ( 1,
             $self->loc( "[_1] is no longer a [_2] for this asset.",
                         $principal->Object->Name,
                         $args{'Type'} ) );
}



# }}}

# {{{ Routines dealing with ACCESS CONTROL

# {{{ sub CurrentUserHasRight

=head2 CurrentUserHasRight

  Takes the textual name of a Asset scoped right (from RT::ACE) and returns
1 if the user has that right. It returns 0 if the user doesn't have that right.

=cut

sub CurrentUserHasRight {
    my $self  = shift;
    my $right = shift;

    return (
        $self->HasRight(
            Principal => $self->CurrentUser->UserObj(),
            Right     => "$right"
          )
    );

}

# }}}

# {{{ sub HasRight

=head2 HasRight

 Takes a paramhash with the attributes 'Right' and 'Principal'
  'Right' is a asset-scoped textual right from RT::ACE
  'Principal' is an RT::User object

  Returns 1 if the principal has the right. Returns undef if not.

=cut

sub HasRight {
    my $self = shift;
    my %args = (
        Right     => undef,
        Principal => undef,
        @_
    );

    unless ( ( defined $args{'Principal'} ) and ( ref( $args{'Principal'} ) ) )
    {
        Carp::cluck;
        $RT::Logger->crit("Principal attrib undefined for Asset::HasRight");
        return(undef);
    }

    return (
        $args{'Principal'}->HasRight(
            Object => $self,
            Right     => $args{'Right'}
          )
    );
}

# }}}

# }}}

# {{{ sub TypeObj

=head2 TypeObj

Takes nothing. returns this asset's type object

=cut

sub TypeObj {
    my $self = shift;

    my $type_obj = RTx::AssetTracker::Type->new( $self->CurrentUser );

    #We call __Value so that we can avoid the ACL decision and some deep recursion
    my ($result) = $type_obj->Load( $self->__Value('Type') );
    return ($type_obj);
}

# }}}

=head2 ParentObj

Takes nothing. returns this asset's parent object

=cut

sub ParentObj {
    my $self = shift;

    my $parent_obj = RTx::AssetTracker::Asset->new( $self->CurrentUser );

    #We call __Value so that we can avoid the ACL decision and some deep recursion
    my ($result) = $parent_obj->Load( $self->__Value('Parent') ) if $self->__Value('Parent');
    return ($parent_obj);
}

# }}}

# {{{ sub AdminGroup

=head2 AdminGroup

Takes nothing.
Returns an RT::Group object which contains this asset's AdminCcs.
If the user doesn't have "ShowAsset" permission, returns an empty group

=cut

sub AdminGroup {
    my $self = shift;

    my $group = RTx::AssetTracker::Group->new($self->CurrentUser);
    if ( $self->CurrentUserHasRight('ShowAsset') ) {
        $group->LoadAssetRoleGroup(Type => 'Admin', Asset => $self->Id);
    }
    return ($group);

}

# }}}

sub _LookupTypes {
    "RTx::AssetTracker::Type-RTx::AssetTracker::Asset";
}

# {{{ sub _Set

sub _Set {
    my $self = shift;

    my %args = ( Field             => undef,
                 Value             => undef,
                 TimeTaken         => 0,
                 RecordTransaction => 1,
                 UpdateAsset      => 1,
                 CheckACL          => 1,
                 TransactionType   => 'Set',
                 @_ );

    if ($args{'CheckACL'}) {
      unless ( $self->CurrentUserHasRight('ModifyAsset')) {
          return ( 0, $self->loc("Permission Denied"));
      }
   }

    unless ($args{'UpdateAsset'} || $args{'RecordTransaction'}) {
        $RT::Logger->error("Asset->_Set called without a mandate to record an update or update the asset");
        return(0, $self->loc("Internal Error"));
    }

    #if the user is trying to modify the record

    #Take care of the old value we really don't want to get in an ACL loop.
    # so ask the super::_Value
    my $Old = $self->SUPER::_Value("$args{'Field'}");

    my ($ret, $msg);
    if ( $args{'UpdateAsset'}  ) {

        #Set the new value
        ( $ret, $msg ) = $self->SUPER::_Set( Field => $args{'Field'},
                                                Value => $args{'Value'} );

        #If we can't actually set the field to the value, don't record
        # a transaction. instead, get out of here.
        if ( $ret == 0 ) { return ( 0, $msg ); }
    }

    if ( $args{'RecordTransaction'} == 1 ) {

        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
                                               Type => $args{'TransactionType'},
                                               Field     => $args{'Field'},
                                               NewValue  => $args{'Value'},
                                               OldValue  => $Old,
                                               #TimeTaken => $args{'TimeTaken'},
                                               TimeTaken => undef,
        );
        return ( $Trans, scalar $TransObj->Description );
    }
    else {
        return ( $ret, $msg );
    }
}

# }}}

# {{{ sub _Value

=head2 _Value

Takes the name of a table column.
Returns its value as a string, if the user passes an ACL check

=cut

sub _Value {

    my $self  = shift;
    my $field = shift;

    #if the field is public, return it.
    if ( $self->_Accessible( $field, 'public' ) ) {

        #$RT::Logger->debug("Skipping ACL check for $field\n");
        return ( $self->SUPER::_Value($field) );

    }

    #If the current user doesn't have ACLs, don't let em at it.

    unless ( $self->CurrentUserHasRight('ShowAsset') ) {
        return (undef);
    }
    return ( $self->SUPER::_Value($field) );

}

# }}}

# {{{ sub CustomFieldValues

=head2 CustomFieldValues

# Do name => id mapping (if needed) before falling back to
# RT::Record's CustomFieldValues

See L<RT::Record>

=cut

sub CustomFieldValues {
    my $self  = shift;
    my $field = shift;
    unless ( $field =~ /^\d+$/ ) {
        my $cf = RTx::AssetTracker::CustomField->new( $self->CurrentUser );
        $cf->LoadByNameAndType( Name => $field, Type => $self->TypeObj->Id );
        unless ( $cf->id ) {
            $cf->LoadByNameAndType( Name => $field, Type => '0' );
        }
        $field = $cf->id;
    }
    return $self->SUPER::CustomFieldValues($field);
}

# }}}

sub IPs {
    my $self = shift;
    my $ips = RTx::AssetTracker::IPs->new( $self->CurrentUser );
    #$ips->UnLimit;
    $ips->Limit( FIELD => 'Asset', VALUE => $self->Id );

    return $ips;
}


sub DeleteIP {

    my $self = shift;
    my %args = ( IP        => undef,
                 @_ );

    unless ( $self->CurrentUserHasRight('ModifyAsset') || $self->CurrentUserHasRight('RetireAsset') ) {
        return ( 0, $self->loc("Permission Denied") );
    }

    my $ip = RTx::AssetTracker::IP->new( $self->CurrentUser );
    $ip->Load( $args{IP} );
    my $addr = $ip->IP;

    # If we can't find this IP, we need to bail.
    unless ( $ip->Id ) {
        return ( 0, $self->loc("Could not find that IP") );
    }

    my $retval = $ip->Delete();
    if ($retval) {
        unless ( $args{'Silent'} ) {
            $self->_NewTransaction( Type     => 'DelIP',
                                    OldValue => $addr,
                                    Field    => 'IP' );
        }

        return ( 1, $self->loc( "$addr is no longer an IP for this asset." ) );
    } else {
        return(0, $self->loc("IP address could not be deleted"));
    }

}

sub AddIP {

    my $self = shift;
    my %args = ( IP        => undef,
                 Interface => undef,
                 MAC => undef,
                 @_ );

    unless ( $self->CurrentUserHasRight('ModifyAsset') ) {
        return ( 0, $self->loc("Permission Denied") );
    }

    if ( $self->Status eq 'retired') {
        return ( 0, $self->loc("Retired assets cannot have IP addresses") );
    }

    unless ($args{IP} or $args{Interface}) {
        return ( 0, $self->loc("IP address or interface must be specified") );
    }

    my $ip = RTx::AssetTracker::IP->new( $self->CurrentUser );
    my ($rc, $msg) = $ip->Create( IP => $args{IP}, Interface => $args{Interface}, MAC => $args{MAC}, Asset => $self->Id );

    if ($ip->Id) {
        unless ( $args{'Silent'} ) {
            $self->_NewTransaction( Type     => 'AddIP',
                                    NewValue => $args{IP},
                                    Field    => 'IP' );
        }
        return ( 1, $self->loc( "$args{IP} is now an IP for this asset." ) );
    } else {
        return( 0, $self->loc("IP address could not be created: $msg"));
    }

}

sub IPsAsList {

    my $self = shift;
    my $ips = $self->IPs;
    my @ips;
    while (my $ip = $ips->Next) {
	push @ips, $ip->IP;
    }

    return @ips;

}

sub IPsAsString {

    my $self = shift;

    return join(',', $self->IPsAsList);
}

# {{{ sub SetStatus

=head2 SetStatus STATUS

Set this asset\'s status. STATUS can be one of: 

Alternatively, you can pass in a list of named parameters (Status => STATUS, Force => FORCE).  If FORCE is true, ignore unresolved dependencies and force a status change.

=begin testing

=end testing


=cut

sub SetStatus {
    my $self   = shift;
    my %args;

    if (@_ == 1) {
        $args{Status} = shift;
    }
    else {
        %args = (@_);
    }

    #Check ACL
    if ( $args{Status} eq 'retired') {
            unless ($self->CurrentUserHasRight('RetireAsset')) {
            return ( 0, $self->loc('Permission Denied') );
       }
        # We don't want reired assets to have IP addresses
        my $ips = $self->IPs();
        while (my $ip = $ips->Next) {
            my($a, $b) = $self->DeleteIP( IP => $ip->IP );
        }
    } else {
            unless ($self->CurrentUserHasRight('ModifyAsset')) {
            return ( 0, $self->loc('Permission Denied') );
       }
    }

    #if (!$args{Force} && ($args{'Status'} eq 'resolved') && $self->HasUnresolvedDependencies) {
        #return (0, $self->loc('That ticket has unresolved dependencies'));
    #}

    #my $now = RT::Date->new( $self->CurrentUser );
    #$now->SetToNow();

    #If we're changing the status from new, record that we've started
    #if ( ( $self->Status =~ /new/ ) && ( $args{Status} ne 'new' ) ) {

        #Set the Started time to "now"
        #$self->_Set( Field             => 'Started',
                     #Value             => $now->ISO,
                     #RecordTransaction => 0 );
    #}

    #When we close a ticket, set the 'Resolved' attribute to now.
    # It's misnamed, but that's just historical.
    #if ( $self->QueueObj->IsInactiveStatus($args{Status}) ) {
        #$self->_Set( Field             => 'Resolved',
                     #Value             => $now->ISO,
                     #RecordTransaction => 0 );
    #}

    #Actually update the status
   my ($val, $msg)= $self->_Set( Field           => 'Status',
                          Value           => $args{Status},
                          TimeTaken       => 0,
                          TransactionType => 'Status'  );

    return($val,$msg);
}

# }}}

# {{{ sub _Links

sub _Links {
    my $self = shift;

    #TODO: Field isn't the right thing here. but I ahave no idea what mnemonic ---
    #tobias meant by $f
    my $field = shift;
    my $type  = shift || "";

    unless ( $self->{"$field$type"} ) {
        $self->{"$field$type"} = new RT::Links( $self->CurrentUser );
        if ( $self->CurrentUserHasRight('ShowAsset') ) {
            # at least to myself
            $self->{"$field$type"}->Limit( FIELD => $field,
                                           VALUE => $self->URI );
            $self->{"$field$type"}->Limit( FIELD => 'Type',
                                           VALUE => $type )
              if ($type);
        }
    }
    return ( $self->{"$field$type"} );
}

# }}}

# {{{ sub DeleteLink

=head2 DeleteLink

Delete a link. takes a paramhash of Base, Target and Type.
Either Base or Target must be null. The null value will
be replaced with this ticket\'s id

=cut

sub DeleteLink {
    my $self = shift;
    my %args = (
        Base   => undef,
        Target => undef,
        Type   => undef,
        @_
    );

    #check acls
    unless ( $self->CurrentUserHasRight('ModifyAsset') ) {
        $RT::Logger->debug("No permission to delete links\n");
        return ( 0, $self->loc('Permission Denied'))

    }

    my ($val, $Msg) = $self->SUPER::_DeleteLink(%args);

    if ( !$val ) {
        $RT::Logger->debug("Couldn't find that link\n");
        return ( 0, $Msg );
    }

    my ($direction, $remote_link);

    if ( $args{'Base'} ) {
        $remote_link = $args{'Base'};
        $direction = 'Target';
    }
    elsif ( $args{'Target'} ) {
        $remote_link = $args{'Target'};
        $direction='Base';
    }


    if ( $val ) {
        my $remote_uri = RT::URI->new( $RT::SystemUser );
        $remote_uri->FromURI( $remote_link );

        my ( $Trans, $Msg, $TransObj ) = $self->_NewTransaction(
            Type      => 'DeleteLink',
            Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
            OldValue =>  $remote_uri->URI || $remote_link,
            TimeTaken => 0
        );

        return ( $Trans, $Msg );
    }
}

# }}}

# {{{ sub AddLink

=head2 AddLink

Takes a paramhash of Type and one of Base or Target. Adds that link to this ticket.

=begin testing


=end testing

=cut

sub AddLink {
    my $self = shift;
    my %args = ( Target => '',
                 Base   => '',
                 Type   => '',
                 Silent => undef,
                 @_ );


    unless ( $self->CurrentUserHasRight('ModifyTicket') ) {
        return ( 0, $self->loc("Permission Denied") );
    }


    $self->_AddLink(%args);
}

=head2 _AddLink

Private non-acled variant of AddLink so that links can be added during create.

=cut

sub _AddLink {
    my $self = shift;
    my %args = ( Target => '',
                 Base   => '',
                 Type   => '',
                 Silent => undef,
                 @_ );

    # {{{ If the other URI is an RT::Ticket, we want to make sure the user
    # can modify it too...
    my $other_ticket_uri = RT::URI->new($self->CurrentUser);

    if ( $args{'Target'} ) {
        $other_ticket_uri->FromURI( $args{'Target'} );

    }
    elsif ( $args{'Base'} ) {
        $other_ticket_uri->FromURI( $args{'Base'} );
    }

    if ( $other_ticket_uri->Resolver->Scheme eq 'at') {
        my $object = $other_ticket_uri->Resolver->Object;

        if (   UNIVERSAL::isa( $object, 'RTx::AssetTracker::Asset' )
            && $object->id
            && !$object->CurrentUserHasRight('ModifyAsset') )
        {
            return ( 0, $self->loc("Permission Denied") );
        }

    }

    # }}}

    my ($val, $Msg) = $self->SUPER::_AddLink(%args);

    if (!$val) {
        return ($val, $Msg);
    }

    my ($direction, $remote_link);
    if ( $args{'Target'} ) {
        $remote_link  = $args{'Target'};
        $direction    = 'Base';
    } elsif ( $args{'Base'} ) {
        $remote_link  = $args{'Base'};
        $direction    = 'Target';
    }

    # Don't write the transaction if we're doing this on create
    if ( $args{'Silent'} ) {
        return ( 1, $Msg );
    }
    else {
        my $remote_uri = RT::URI->new( $RT::SystemUser );
        $remote_uri->FromURI( $remote_link );

        #Write the transaction
        my ( $Trans, $Msg, $TransObj ) =
            $self->_NewTransaction(Type  => 'AddLink',
                                   Field => $LINKDIRMAP{$args{'Type'}}->{$direction},
                                   NewValue =>  $remote_uri->URI || $remote_link,
                                   TimeTaken => 0 );
        return ( $Trans, $Msg );
    }

}

# }}}


1;
